import java.util.ArrayList;
import java.util.LinkedList;

public class MultiLevel {

	private ArrayList<Process> processes;
	private ArrayList<LinkedList<Process>> multi;
	private int time = 0;
	private boolean running;
	private boolean readyToRun;

	public MultiLevel() {
		processes = new ArrayList<Process>();
		multi = new ArrayList<LinkedList<Process>>(4);
		LinkedList<Process> lvl1 = new LinkedList<Process>();
		LinkedList<Process> lvl2 = new LinkedList<Process>();
		LinkedList<Process> lvl4 = new LinkedList<Process>();
		LinkedList<Process> lvl8 = new LinkedList<Process>();
		multi.add(lvl1);
		multi.add(lvl2);
		multi.add(lvl4);
		multi.add(lvl8);
		running = false;
		setReadyToRun(true);
	}

	/**
	 * given an ArrayList of processes, this method runs them with the
	 * MultiLevel FeedBack scheduling algorithm
	 * 
	 * @param p
	 *            , Array List of processes
	 */
	public void simulate(ArrayList<Process> p) {
		p = sortIntoList(p);
		int endRunTime = 0;
		int timeCount = 1;
		int Q = 0;
		for (time = 0; !isMultiEmpty() || !p.isEmpty(); time++) {
			System.out.println("time = " + time);
			if (!p.isEmpty()) {
				if (p.get(0).getArrivaltime() == time) {// adds the newly
														// arrived
					// processes to the top
					// level
					multi.get(0).add(p.get(0));
					System.out.println("Process " + p.get(0).getName()
							+ " Arrived");
					p.remove(0);
				}
			}
			int index = 0;
			boolean gotIndex = false;
			if (running) {// gets the index of the running process
				for (int j = 0; j < multi.size(); j++) {
					for (int k = 0; k < multi.get(j).size(); k++) {
						if (!gotIndex) {
							if (multi.get(j).get(k).isRunning()) {
								index = j;
								gotIndex = true;
							}
						}
					}
				}
				System.out.println("Process "
						+ multi.get(index).getFirst().getName()
						+ " is in a running state");
			} else { // get the index of the process that is intended to run
				for (int j = 0; j < multi.size(); j++) {
					for (int k = 0; k < multi.get(j).size(); k++) {
						if (!gotIndex) {
							if (!multi.get(j).isEmpty()) {
								index = j;
								gotIndex = true;
							}
						}
					}
				}
			}
			if (readyToRun && !multi.get(index).isEmpty()) { // initiates the
				// running of
				// the process
				readyToRun = false;
				running = true;
				if (index == 0) { // sets the quantum for the intended level
					Q = 1;
					System.out.println("Q = " + Q);
				}
				if (index == 1) {
					Q = 2;
					System.out.println("Q = " + Q);
				}
				if (index == 2) {
					Q = 4;
					System.out.println("Q = " + Q);
				}
				if (index == 3) {
					Q = 8;
					System.out.println("Q = " + Q);
				}
				multi.get(index).getFirst().setRunning(true);
				multi.get(index).getFirst().setRemainingTime(
						multi.get(index).getFirst().getRemainingTime() - 1);// runs
				// the
				// process
				// for
				// Q
				// time
				// units
				multi.get(index).getFirst().setRanFor(
						multi.get(index).getFirst().getRanFor() + 1);
				endRunTime = time + Q;
				timeCount = 1;
				System.out.println("Process "
						+ multi.get(index).getFirst().getName()
						+ " is scheduled for " + Q + " time units.");
			}
			if (running) {// runs the intended process
				if (endRunTime == time || timeCount == Q
						|| multi.get(index).getFirst().getRemainingTime() == 0) {
					running = false;
					readyToRun = true;
					multi.get(index).getFirst().setRunning(false);
					timeCount = 0;
				}
				if (timeCount < Q
						&& multi.get(index).getFirst().getRanFor() != 1) {
					timeCount++;
					endRunTime++;
					multi.get(index).getFirst().setRemainingTime(
							multi.get(index).getFirst().getRemainingTime() - 1);
					multi.get(index).getFirst().setRanFor(
							multi.get(index).getFirst().getRanFor() + 1);
					System.out.println("Process "
							+ multi.get(index).getFirst().getName()
							+ " ran for " + timeCount + " time units");
				}
			}
			if (multi.get(index).getFirst().getRanFor() < multi.get(index)
					.getFirst().getRuntime()
					&& !running) { // this part for rearranging the processes in
				// the 2D array, checks if process is done
				// running
				System.out.println("Process "
						+ multi.get(index).getFirst().getName()
						+ " is going down a level");
				Process temp = multi.get(index).getFirst();
				multi.get(index).remove();
				if (multi.size() > index + 1) // checks if there is a next level
					multi.get(index + 1).add(temp);
				else {
					multi.get(index).add(temp);
				}
			} else if (multi.get(index).getFirst().getRanFor() >= multi.get(
					index).getFirst().getRuntime()
					&& !multi.get(index).isEmpty()
					&& !multi.get(index).getFirst().isRunning()) { // removes
				// the
				// process
				// if done
				// running
				System.out.println("Removing process "
						+ multi.get(index).getFirst().getName());
				multi.get(index).remove();
			}
		}
	}

	/**
	 * sorts the processes into an ArrayList by arrival time in ascending order
	 * 
	 * @param p
	 * @return
	 */
	public ArrayList<Process> sortIntoList(ArrayList<Process> p) {
		int temp;
		for (int i = 0; i < p.size(); i++) {
			for (int j = 0; j < p.size() - i - 1; j++) {
				if (p.get(j + 1).getArrivaltime() < p.get(j).getArrivaltime()) {
					temp = p.get(j).getArrivaltime();
					p.get(j).setArrivaltime(p.get(j + 1).getArrivaltime());
					p.get(j + 1).setArrivaltime(temp);
				}
			}
		}
		return p;
	}

	/**
	 * checks if the whole arrayList of LinkedLists is empty or not
	 * 
	 * @return
	 */
	public boolean isMultiEmpty() {
		boolean lvl1, lvl2, lvl3, lvl4;
		lvl1 = false;
		lvl2 = false;
		lvl3 = false;
		lvl4 = false;
		for (int i = 0; i < multi.size(); i++) {
			if (i == 0 && multi.get(i).isEmpty())
				lvl1 = true;
			if (i == 1 && multi.get(i).isEmpty())
				lvl2 = true;
			if (i == 2 && multi.get(i).isEmpty())
				lvl3 = true;
			if (i == 3 && multi.get(i).isEmpty())
				lvl4 = true;
		}
		if (lvl1 == true && lvl2 == true && lvl3 == true && lvl4 == true)
			return true;
		else
			return false;
	}

	public boolean addProcess(Process p) {
		return processes.add(p);
	}

	public void setProcesses(ArrayList<Process> processes) {
		this.processes = processes;
	}

	public ArrayList<Process> getProcesses() {
		return processes;
	}

	public void setMulti(ArrayList<LinkedList<Process>> multi) {
		this.multi = multi;
	}

	public ArrayList<LinkedList<Process>> getMulti() {
		return multi;
	}

	public void setTime(int time) {
		this.time = time;
	}

	public int getTime() {
		return time;
	}

	public boolean isRunning() {
		return running;
	}

	public void setRunning(boolean running) {
		this.running = running;
	}

	public void setReadyToRun(boolean readyToRun) {
		this.readyToRun = readyToRun;
	}

	public boolean isReadyToRun() {
		return readyToRun;
	}
}